package com.bstek.uflo.form.view.common;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.htmlparser.Node;
import org.htmlparser.Parser;
import org.htmlparser.nodes.TagNode;
import org.htmlparser.util.NodeIterator;
import org.htmlparser.util.NodeList;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.SerializationUtils;
import org.springframework.util.StringUtils;

import com.bstek.dorado.annotation.DataProvider;
import com.bstek.dorado.annotation.DataResolver;
import com.bstek.dorado.data.provider.manager.DataProviderManager;
import com.bstek.dorado.data.type.AggregationDataType;
import com.bstek.dorado.data.type.DataType;
import com.bstek.dorado.data.type.EntityDataType;
import com.bstek.dorado.data.type.property.BasePropertyDef;
import com.bstek.dorado.data.type.property.Mapping;
import com.bstek.dorado.view.View;
import com.bstek.dorado.view.manager.ViewConfig;
import com.bstek.dorado.view.widget.data.DataSet;
import com.bstek.dorado.web.DoradoContext;
import com.bstek.uflo.command.CommandService;
import com.bstek.uflo.form.Constants;
import com.bstek.uflo.form.action.Action;
import com.bstek.uflo.form.action.FormData;
import com.bstek.uflo.form.command.GetTableDataCommand;
import com.bstek.uflo.form.command.GetTableDefinitionCommand;
import com.bstek.uflo.form.command.SaveDataCommand;
import com.bstek.uflo.form.controller.FormHolder;
import com.bstek.uflo.form.model.Form;
import com.bstek.uflo.form.model.MappingProvider;
import com.bstek.uflo.form.model.MappingSource;
import com.bstek.uflo.form.model.TableColumn;
import com.bstek.uflo.form.model.TableDefinition;

/**
 * @author Jacky.gao
 * @since 2013年11月5日
 */
@Component("uflo.form.commonPage")
public class CommonPage implements ApplicationContextAware{
	@Autowired
	@Qualifier(CommandService.BEAN_ID)
	private CommandService commandService;

	@Autowired
	@Qualifier("dorado.dataProviderManager")
	private DataProviderManager dataProviderManager;
	
	private EntityDataType dataTypeMaster;
	private EntityDataType dataTypeSlave;
	private ApplicationContext applicationContext;
	private Collection<ComponentBuilder> builders;
	public void onInit(ViewConfig config) throws Exception{
		HttpServletRequest request=DoradoContext.getCurrent().getRequest();
		String tableId=request.getParameter("tableId");
		if(StringUtils.isEmpty(tableId)){
			return;
		}
		TableDefinition table=commandService.executeCommand(new GetTableDefinitionCommand(Long.valueOf(tableId)));
		if(table==null){
			throw new IllegalArgumentException("Table "+tableId+" is not exist.");
		}
		dataTypeMaster=(EntityDataType)config.createDataType("dataTypeMaster");
		Map<String,Boolean> mappingMap=new HashMap<String,Boolean>();
		buildDataTypeProperty(config, table,dataTypeMaster,mappingMap);
		if(table.getSlaveTables().size()>0){
			TableDefinition slaveTable=table.getSlaveTables().get(0);
			dataTypeSlave=(EntityDataType)config.createDataType("dataTypeSlave");
			buildDataTypeProperty(config, slaveTable,dataTypeSlave,null);
			BasePropertyDef salveProperty=new BasePropertyDef(Constants.SLAVE_PROPERTY);
			AggregationDataType refDataType=new AggregationDataType();
			refDataType.setId("[dataTypeSlave]");
			refDataType.setElementDataType(dataTypeSlave);
			salveProperty.setDataType(refDataType);
			dataTypeMaster.addPropertyDef(salveProperty);
		}
		View view=config.getView();
		Form form=FormHolder.getForm();
		if(form==null){
			throw new IllegalArgumentException("Invalid request.");
		}
		String content=(String)SerializationUtils.deserialize(form.getContent());
		String html="<body>"+content+"</body>";
		Map<String,com.bstek.dorado.view.widget.Component> map=new HashMap<String,com.bstek.dorado.view.widget.Component>();
		Parser parser = new Parser();
		parser.setResource(html);
		NodeIterator iterator=parser.elements();
		while(iterator.hasMoreNodes()){
			Node node=iterator.nextNode();
			parseXml(node,view,map,mappingMap,table);
		}
		DataSet ds=new DataSet();
		ds.setId(Constants.DATASET);
		ds.setDataType(dataTypeMaster);
		Map<String,Object> parameter=new HashMap<String,Object>();
		String businessId=request.getParameter("businessId");
		parameter.put("businessId", businessId);
		parameter.put("tableId", Long.valueOf(tableId));
		ds.setParameter(parameter);
		ds.setDataProvider(dataProviderManager.getDataProvider("uflo.form.commonPage#loadTableData"));
		view.addChild(ds);
	}
	
	private void parseXml(Node node,View view,Map<String,com.bstek.dorado.view.widget.Component> map,Map<String,Boolean> mappingMap,TableDefinition table) throws Exception{
		if(!(node instanceof TagNode))return;
		TagNode element=(TagNode)node;
		for(ComponentBuilder builder:builders){
			if(builder.support(element)){
				builder.build(element, view, map,mappingMap,table);
				break;
			}
		}
		NodeList children=element.getChildren();
		if(children!=null && children.size()>0){
			for(int i=0;i<children.size();i++){
				Node cnode=children.elementAt(i);
				parseXml(cnode,view,map,mappingMap,table);
			}
		}
	}
	
	private void buildDataTypeProperty(ViewConfig config, TableDefinition table,EntityDataType dataType,Map<String,Boolean> mappingMap) throws Exception {
		BasePropertyDef primaryKeyDef=new BasePropertyDef(Constants.PRIMARY_KEY_COLUMN);
		primaryKeyDef.setDataType(config.getDataType("long"));
		dataType.addPropertyDef(primaryKeyDef);
		for(TableColumn col:table.getColumns()){
			BasePropertyDef def=new BasePropertyDef(col.getName().toLowerCase());
			def.setLabel(col.getMemo());
			def.setDataType(getDataType(config,col.getDataType()));
			def.setDefaultValue(col.getDefaultValue());
			def.setRequired(!col.isEmpty());
			String mappingStr=col.getMapping();
			if(StringUtils.hasText(mappingStr)){
				def.setMapping(buildMapping(col.getMapping(),col.getMappingSource()));
				if(mappingMap!=null)mappingMap.put(col.getName(), true);
			}else{
				if(mappingMap!=null)mappingMap.put(col.getName(), false);
			}
			dataType.addPropertyDef(def);
		}
	}
	
	private Mapping buildMapping(String mappingStr,MappingSource mappingSource){
		Mapping mapping=new Mapping();
		mapping.setKeyProperty("key");
		mapping.setValueProperty("label");
		if(mappingSource.equals(MappingSource.MappingProvider)){
			MappingProvider provider=(MappingProvider)applicationContext.getBean(mappingStr);
			mapping.setMapValues(provider.getMapping());
		}else{
			List<Map<String,Object>> list=new ArrayList<Map<String,Object>>();
			for(String pairs:mappingStr.split(";")){
				String[] pair=pairs.split("=");
				if(pair.length==2){
					Map<String,Object> map=new HashMap<String,Object>();
					map.put("key", pair[0]);					
					map.put("label", pair[1]);	
					list.add(map);
				}
			}
			mapping.setMapValues(list);
		}
		
		return mapping;
	}
	
	private DataType getDataType(ViewConfig config,String dataType) throws Exception{
		String type=null;
		dataType=dataType.toLowerCase();
		if(dataType.startsWith("varchar") || dataType.startsWith("nvarchar")){
			type="String";
		}
		if(dataType.startsWith("double")){
			type="double";
		}
		if(dataType.startsWith("float")){
			type="float";
		}
		if(dataType.startsWith("bit")){
			type="boolean";
		}
		if(dataType.startsWith("long")){
			type="long";
		}
		if(dataType.startsWith("date")){
			type="Date";
		}
		if(dataType.startsWith("int") || dataType.startsWith("smallint") || dataType.startsWith("tinyint") || dataType.startsWith("integer")){
			type="int";
		}
		if(dataType.startsWith("number") && dataType.indexOf("(")>-1){
			int start=dataType.indexOf("(")+1;
			int end=dataType.lastIndexOf(")");
			String[] numbers=dataType.substring(start,end).split(",");
			if(numbers.length==1){
				type="long";				
			}else if(Integer.parseInt(numbers[1])>0){
				type="double";								
			}else{
				type="long";				
			}
		}
		if(dataType.startsWith("numeric") && dataType.indexOf("(")>-1){
			int start=dataType.indexOf("(")+1;
			int end=dataType.lastIndexOf(")");
			String[] numbers=dataType.substring(start,end).split(",");
			if(numbers.length==1){
				type="long";				
			}else if(Integer.parseInt(numbers[1])>0){
				type="double";								
			}else{
				type="long";				
			}
		}
		if(type==null){
			throw new IllegalArgumentException("Unsupport datatype:"+dataType+"");
		}
		return config.getDataType(type);
	}
	
	@DataProvider
	public Map<String,Object> loadTableData(String businessId,long tableId){
		if(StringUtils.isEmpty(businessId)){
			return new HashMap<String,Object>();
		}
		TableDefinition table=commandService.executeCommand(new GetTableDefinitionCommand(tableId));
		Object keyValue=businessId;
		String tableName=table.getName();
		String sql="select * from "+tableName+" where "+Constants.PRIMARY_KEY_COLUMN+"=?";
		List<TableDefinition> slaveTables=table.getSlaveTables();
		if(slaveTables!=null && slaveTables.size()>0){
			TableDefinition slaveTable=slaveTables.get(0);
			String slaveSql="select * from "+slaveTable.getName()+" where "+Constants.MASTER_ID+"=?";
			return commandService.executeCommand(new GetTableDataCommand(sql,slaveSql, keyValue));
		}else{
			return commandService.executeCommand(new GetTableDataCommand(sql,null, keyValue));			
		}
	}
	
	@SuppressWarnings("unchecked")
	@DataResolver
	public void saveData(Map<String,Object> data,Map<String,Object> parameter){
		String actionStr=(String)parameter.get("action");
		String tableId=(String)parameter.get("tableId");
		String taskId=(String)parameter.get("taskId");
		FormData formData=new FormData();
		if(StringUtils.hasText(taskId)){
			formData.setTaskId(Long.valueOf(taskId));
		}
		String processId=(String)parameter.get("processId");
		if(StringUtils.hasText(processId)){
			formData.setProcessId(Long.valueOf(processId));
		}
		Object processData=parameter.get("processData");
		if(processData!=null){
			formData.setProcessData((Map<String, Object>)processData);
		}
		Action action=(Action)applicationContext.getBean(actionStr);
		TableDefinition table=commandService.executeCommand(new GetTableDefinitionCommand(Long.valueOf(tableId)));
		formData.setMasterTable(data);
		formData.setTable(table);
		Object slaveData=data.get(Constants.SLAVE_PROPERTY);
		if(slaveData!=null){
			formData.setSlaveTable((Collection<Map<String,Object>>)slaveData);
		}
		commandService.executeCommand(new SaveDataCommand(action,formData));
	}
	
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext=applicationContext;
		builders=applicationContext.getBeansOfType(ComponentBuilder.class).values();
	}
}
